using System;
using CodeAngel.Domain.Framework;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace CodeAngel.Domain
{
	public partial class Domain
	{

		#region SetDefaultPropertyValues

        /// <summary>
        /// Sets the properties of a new entity to its default values.
        /// </summary>
		internal override void SetDefaultPropertyValues()
		{
            this.SetFriendlyIdAsync();
            this.DomainID = Guid.NewGuid();

            this.DevelopmentDatabase = new Database(this.Aggregate, this);
            this.DevelopmentDatabase.InitializeAllProperties();
            this.DevelopmentDatabase.SetDefaultPropertyValues();

            this.ProductionDatabase = new Database(this.Aggregate, this);
            this.ProductionDatabase.InitializeAllProperties();
            this.ProductionDatabase.SetDefaultPropertyValues();

            this.AddSecurityEntityDefs();
		}

        /// <summary>
        /// Creates the Develoment database, if it does not already exist.
        /// </summary>
        public void CreateDevelopmentDatabase()
        {
            if (this.DevelopmentDatabase != null) return;
            this.DevelopmentDatabase = new Database(this.Aggregate, this);
            this.DevelopmentDatabase.InitializeAllProperties();
            this.DevelopmentDatabase.SetDefaultPropertyValues();
        }

        /// <summary>
        /// Creates the Develoment database, if it does not already exist.
        /// </summary>
        public void CreateProductionDatabase()
        {
            if (this.ProductionDatabase != null) return;
            this.ProductionDatabase = new Database(this.Aggregate, this);
            this.ProductionDatabase.InitializeAllProperties();
            this.ProductionDatabase.SetDefaultPropertyValues();
        }

		#endregion

        #region CreateOrUpdateFilesAsync

        /// <summary>
        /// Creates or updates all generated files for the current domain.
        /// </summary>
        public Task[] CreateOrUpdateFilesAsync()
        {
            var Tasks = new List<Task>();

            Tasks.Add(this.CreateDtoExtensionsFileAsync());
            Tasks.Add(this.CreateDtoHostFileAsync());
            Tasks.Add(this.CreateAggregateExtensionsFileAsync());

            foreach (var ThisED in this.EntityDefs)
            {
                foreach (var ThisAD in ThisED.AggregateDefs)
                    Tasks.Add(ThisAD.CreateAggregateFileAsync());

                Tasks.Add(ThisED.CreateDtoFileAsync());
                Tasks.Add(ThisED.CreateEntityFilesAsync());
                Tasks.Add(ThisED.CreateEntityCollectionFilesAsync());
                Tasks.Add(ThisED.CreateFilterFilesAsync());
                Tasks.Add(ThisED.CreateCriteriaFileAsync());
                Tasks.Add(ThisED.CreateDeleteAggregateFileAsync());
            }

            return Tasks.ToArray();
        }

        #endregion

        #region Directories

        /// <summary>
        /// Gets the path to the domain project directory.
        /// </summary>
        public string ProjectPath
        {
            get { return System.IO.Path.Combine(GetVisualStudioProjectsDirectory(), this.ProjectRelativePath); }
        }

        /// <summary>
        /// Gets the path to the domain entities directory.
        /// </summary>
        public string EntitiesPath
        {
            get { return System.IO.Path.Combine(this.ProjectPath, "Entities"); }
        }

        /// <summary>
        /// Gets the path to the full CLR project directory.
        /// </summary>
        public string ServicesProjectPath
        {
            get { return System.IO.Path.Combine(GetVisualStudioProjectsDirectory(), this.ServicesProjectRelativePath); }
        }

        /// <summary>
        /// Gets the path to the service's extensions directory.
        /// </summary>
        public string ServicesExtensionsPath
        {
            get { return System.IO.Path.Combine(this.ServicesProjectPath, "Extensions"); }
        }


        /// <summary>
        /// Gets the directory where all Visual Studio projects get stored.
        /// </summary>
        private string GetVisualStudioProjectsDirectory()
        {
            // TODO: localize path to develoers computer
            //Return My.Application.GetEnvironmentVariable("VisualStudioProjectsDirectory")
            return "C:\\Users\\Adam\\Projects";
        }

        #endregion

        #region Security

        /// <summary>
        /// Adds the entity definitions used to control access to this domain (e.g., User, Role, Permission, etc).
        /// </summary>
        public void AddSecurityEntityDefs()
        {
            this.CreatePermissionEntityDef();

            this.CreateUserEntityDef();
            this.CreateUser_RoleEntityDef();
            this.CreateUser_PermissionEntityDef();
            this.CreateUser_DeniedPermissionEntityDef();

            this.CreateRoleEntityDef();
            this.CreateRole_PermissionEntityDef();

            this.CreateUserEntityDefRelationships();
            this.CreateRoleEntityDefRelationships();
        }

        #region User

        private void CreateUserEntityDef()
        {
            if (this.EntityDefExists("User")) return;

            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "User";
            ThisED.Purpose = "Represents a user who may access this system. This entity is used to identify the credentials and permissions for a particular user.  A user must have UserName and a Password, and can be assigned zero or more Permissions, can be explicitly denied zero or more permissions, and can be added to zero or more Roles.";
            ThisED.CanBeAggregateRoot = true;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();
            ThisED.AddPropertyDef_FriendlyID();

            // UserName
            var UserNamePD = ThisED.PropertyDefs.AddNew();
            UserNamePD.Name = "UserName";
            UserNamePD.FriendlyName = "UserName";
            UserNamePD.DatabaseColumnName = "UserName";
            UserNamePD.Purpose = "To identify the name of this user.";
            UserNamePD.DataType = "string";
            UserNamePD.SqlDataType = "varcharmax";
            //UserNamePD.SqlDataLength = 100;
            UserNamePD.IsNullable = false;
            //UserNamePD.MustBeUnique = true;

            // Password
            var PasswordPD = ThisED.PropertyDefs.AddNew();
            PasswordPD.Name = "Password";
            PasswordPD.FriendlyName = "Password";
            PasswordPD.DatabaseColumnName = "Password";
            PasswordPD.Purpose = "To identify the password of this user.";
            PasswordPD.DataType = "string";
            PasswordPD.SqlDataType = "varcharmax";
            //PasswordPD.SqlDataLength = 150;
            PasswordPD.IsNullable = false;
        }

        /// <summary>
        /// Adds the User_Role entity definition, which is used to associate a User to a Role.
        /// </summary>
        private void CreateUser_RoleEntityDef()
        {
            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "User_Role";
            ThisED.Purpose = "Associates a User to a Role.  This User will inherit the Permissions associated with that Role.";
            ThisED.CanBeAggregateRoot = false;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();

            // User
            var UserPD = ThisED.PropertyDefs.AddNew();
            UserPD.Name = "User";
            UserPD.FriendlyName = "User";
            UserPD.DatabaseColumnName = "UserID";
            UserPD.Purpose = "Identifies the User.";
            UserPD.DataType = "User";
            UserPD.SqlDataType = "uniqueidentifier";
            UserPD.IsNullable = false;

            // Role
            var RolePD = ThisED.PropertyDefs.AddNew();
            RolePD.Name = "Role";
            RolePD.FriendlyName = "Role";
            RolePD.DatabaseColumnName = "RoleID";
            RolePD.Purpose = "Identifies the Role.";
            RolePD.DataType = "Role";
            RolePD.SqlDataType = "uniqueidentifier";
            RolePD.IsNullable = false;


        }

        /// <summary>
        /// Adds the User_Permission entity definition, which is used to associate a User and a Permission.
        /// </summary>
        private void CreateUser_PermissionEntityDef()
        {
            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "User_Permission";
            ThisED.Purpose = "Associates a User and a Permission.";
            ThisED.CanBeAggregateRoot = false;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();

            // User
            var UserPD = ThisED.PropertyDefs.AddNew();
            UserPD.Name = "User";
            UserPD.FriendlyName = "User";
            UserPD.DatabaseColumnName = "UserID";
            UserPD.Purpose = "Identifies the User.";
            UserPD.DataType = "User";
            UserPD.SqlDataType = "uniqueidentifier";
            UserPD.IsNullable = false;

            // Permission
            var PermissionPD = ThisED.PropertyDefs.AddNew();
            PermissionPD.Name = "Permission";
            PermissionPD.FriendlyName = "Permission";
            PermissionPD.DatabaseColumnName = "PermissionID";
            PermissionPD.Purpose = "Identifies the Permission.";
            PermissionPD.DataType = "Permission";
            PermissionPD.SqlDataType = "uniqueidentifier";
            PermissionPD.IsNullable = false;
        }

        /// <summary>
        /// Adds the User_DeniedPermission entity definition, which is used to associate a User and a DeniedPermission.
        /// </summary>
        private void CreateUser_DeniedPermissionEntityDef()
        {
            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "User_DeniedPermission";
            ThisED.Purpose = "Associates a User and a Permission that they are explicitly denied.  Irregardless of any Role membership that may grant the associated Permission, the User will not be able to perform the respetive operation.";
            ThisED.CanBeAggregateRoot = false;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();

            // User
            var UserPD = ThisED.PropertyDefs.AddNew();
            UserPD.Name = "User";
            UserPD.FriendlyName = "User";
            UserPD.DatabaseColumnName = "UserID";
            UserPD.Purpose = "Identifies the User.";
            UserPD.DataType = "User";
            UserPD.SqlDataType = "uniqueidentifier";
            UserPD.IsNullable = false;

            // DeniedPermission
            var DeniedPermissionPD = ThisED.PropertyDefs.AddNew();
            DeniedPermissionPD.Name = "DeniedPermission";
            DeniedPermissionPD.FriendlyName = "DeniedPermission";
            DeniedPermissionPD.DatabaseColumnName = "DeniedPermissionID";
            DeniedPermissionPD.Purpose = "Identifies the Permission the associated User is being explicitly denied.";
            DeniedPermissionPD.DataType = "Permission";
            DeniedPermissionPD.SqlDataType = "uniqueidentifier";
            DeniedPermissionPD.IsNullable = false;
        }

        /// <summary>
        /// Creates the relationship property definitions between the User and other entity definitions (e.g., Role and Permission).
        /// </summary>
        private void CreateUserEntityDefRelationships()
        {
            var UserED = this.EntityDefs.Find("User");

            // Roles
            var User_RolesPD = UserED.PropertyDefs.AddNew();
            User_RolesPD.Name = "User_Roles";
            User_RolesPD.FriendlyName = "User_Roles";
            User_RolesPD.DatabaseColumnName = "User_RolesID";
            User_RolesPD.Purpose = "Associates this User with a collection of User_Role entities, effectively granting this User membership to the respective Role.";
            User_RolesPD.DataType = "User_RoleCollection";
            User_RolesPD.SqlDataType = "uniqueidentifier";
            User_RolesPD.ForeignKeyPropertyDef = this.EntityDefs.Find("User_Role").PropertyDefs.Find("User");

            // Permissions
            var User_PermissionsPD = UserED.PropertyDefs.AddNew();
            User_PermissionsPD.Name = "User_Permissions";
            User_PermissionsPD.FriendlyName = "User_Permissions";
            User_PermissionsPD.DatabaseColumnName = "User_PermissionsID";
            User_PermissionsPD.Purpose = "Associates this User with a collection of User_Permission entities, effectively granting this User with the respective Permission.";
            User_PermissionsPD.DataType = "User_PermissionCollection";
            User_PermissionsPD.SqlDataType = "uniqueidentifier";
            User_PermissionsPD.ForeignKeyPropertyDef = this.EntityDefs.Find("User_Permission").PropertyDefs.Find("User");

            // DeniedPermissions
            var User_DeniedPermissionsPD = UserED.PropertyDefs.AddNew();
            User_DeniedPermissionsPD.Name = "User_DeniedPermissions";
            User_DeniedPermissionsPD.FriendlyName = "User_DeniedPermissions";
            User_DeniedPermissionsPD.DatabaseColumnName = "User_DeniedPermissionsID";
            User_DeniedPermissionsPD.Purpose = "Associates this User with a collection of User_DeniedPermission entities, explicitly denying this User the respective DeniedPermission.";
            User_DeniedPermissionsPD.DataType = "User_DeniedPermissionCollection";
            User_DeniedPermissionsPD.SqlDataType = "uniqueidentifier";
            User_DeniedPermissionsPD.ForeignKeyPropertyDef = this.EntityDefs.Find("User_DeniedPermission").PropertyDefs.Find("User");
        }

        #endregion

        #region Role

        private void CreateRoleEntityDef()
        {
            if (this.EntityDefExists("Role")) return;

            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "Role";
            ThisED.Purpose = "Reresents a security role of this domain.  A Role is a group of Permissions where one or more Users may be a member of that role.  If a User is a member of the Role, then they will inherit the Permissions of the Role.";
            ThisED.CanBeAggregateRoot = true;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();
            ThisED.AddPropertyDef_FriendlyID();
            ThisED.AddPropertyDef_Name();

            // Description
            var DescriptionPD = ThisED.PropertyDefs.AddNew();
            DescriptionPD.Name = "Description";
            DescriptionPD.FriendlyName = "Description";
            DescriptionPD.DatabaseColumnName = "Description";
            DescriptionPD.Purpose = "A somewhat user-friendly description of this permission.";
            DescriptionPD.DataType = "string";
            DescriptionPD.SqlDataType = "varchar";
            DescriptionPD.SqlDataLength = 250;
            DescriptionPD.IsNullable = false;
        }

        /// <summary>
        /// Adds the Role_Permission entity definition, which is used to associate a Role and a Permission.
        /// </summary>
        private void CreateRole_PermissionEntityDef()
        {
            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "Role_Permission";
            ThisED.Purpose = "Associates a Role and a Permission.";
            ThisED.CanBeAggregateRoot = false;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();

            // Role
            var RolePD = ThisED.PropertyDefs.AddNew();
            RolePD.Name = "Role";
            RolePD.FriendlyName = "Role";
            RolePD.DatabaseColumnName = "RoleID";
            RolePD.Purpose = "Identifies the Role.";
            RolePD.DataType = "Role";
            RolePD.SqlDataType = "uniqueidentifier";
            RolePD.IsNullable = false;

            // Permission
            var PermissionPD = ThisED.PropertyDefs.AddNew();
            PermissionPD.Name = "Permission";
            PermissionPD.FriendlyName = "Permission";
            PermissionPD.DatabaseColumnName = "PermissionID";
            PermissionPD.Purpose = "Identifies the Permission.";
            PermissionPD.DataType = "Permission";
            PermissionPD.SqlDataType = "uniqueidentifier";
            PermissionPD.IsNullable = false;
        }

        /// <summary>
        /// Creates the relationship property definitions between the Role and other entity definitions (e.g., User and Permission).
        /// </summary>
        private void CreateRoleEntityDefRelationships()
        {
            var RoleED = this.EntityDefs.Find("Role");

            // Users
            var User_RolesPD = RoleED.PropertyDefs.AddNew();
            User_RolesPD.Name = "User_Roles";
            User_RolesPD.FriendlyName = "User_Roles";
            User_RolesPD.DatabaseColumnName = "User_RolesID";
            User_RolesPD.Purpose = "Associates this Role with a collection of User_Role entities, effectively granting the respective User membership to this Role.";
            User_RolesPD.DataType = "User_RoleCollection";
            User_RolesPD.SqlDataType = "uniqueidentifier";
            User_RolesPD.ForeignKeyPropertyDef = this.EntityDefs.Find("User_Role").PropertyDefs.Find("Role");

            // Permissions
            var Role_PermissionsPD = RoleED.PropertyDefs.AddNew();
            Role_PermissionsPD.Name = "Role_Permissions";
            Role_PermissionsPD.FriendlyName = "Role_Permissions";
            Role_PermissionsPD.DatabaseColumnName = "Role_PermissionsID";
            Role_PermissionsPD.Purpose = "Associates this Role with a collection of Role_Permission entities, effectively granting this Role with the respective Permission.";
            Role_PermissionsPD.DataType = "Role_PermissionCollection";
            Role_PermissionsPD.SqlDataType = "uniqueidentifier";
            Role_PermissionsPD.ForeignKeyPropertyDef = this.EntityDefs.Find("Role_Permission").PropertyDefs.Find("Role");
        }

        #endregion

        #region Permission

        private void CreatePermissionEntityDef()
        {
            if (this.EntityDefExists("Permission")) return;

            var ThisED = this.EntityDefs.AddNew();
            ThisED.Name = "Permission";
            ThisED.Purpose = "Represents a permission that can be granted or revoked for a parituclar Role or User (e.g. CanReadStudent, CanCreateStudent, etc.).";
            ThisED.CanBeAggregateRoot = true;
            ThisED.SubFolder = "Security";

            ThisED.AddPropertyDef_PrimaryKey();
            ThisED.AddPropertyDef_FriendlyID();
            ThisED.AddPropertyDef_Name();

            // ObjectName
            var ObjectNamePD = ThisED.PropertyDefs.AddNew();
            ObjectNamePD.Name = "ObjectName";
            ObjectNamePD.FriendlyName = "ObjectName";
            ObjectNamePD.DatabaseColumnName = "ObjectName";
            ObjectNamePD.Purpose = "Identifies the object name this permission applies to.";
            ObjectNamePD.DataType = "string";
            ObjectNamePD.SqlDataType = "varchar";
            ObjectNamePD.SqlDataLength = 250;
            ObjectNamePD.IsNullable = false;

            // ObjectType
            var ObjectTypePD = ThisED.PropertyDefs.AddNew();
            ObjectTypePD.Name = "ObjectType";
            ObjectTypePD.FriendlyName = "ObjectType";
            ObjectTypePD.DatabaseColumnName = "ObjectType";
            ObjectTypePD.Purpose = "Identifies the object type this permission applies to.";
            ObjectTypePD.DataType = "string";
            ObjectTypePD.SqlDataType = "varchar";
            ObjectTypePD.SqlDataLength = 50;
            ObjectTypePD.IsNullable = false;

            // OperationType
            var OperationTypePD = ThisED.PropertyDefs.AddNew();
            OperationTypePD.Name = "OperationType";
            OperationTypePD.FriendlyName = "OperationType";
            OperationTypePD.DatabaseColumnName = "OperationType";
            OperationTypePD.Purpose = "Identifies the operation of this permission.";
            OperationTypePD.DataType = "string";
            OperationTypePD.SqlDataType = "varchar";
            OperationTypePD.SqlDataLength = 50;
            OperationTypePD.IsNullable = false;

            // Description
            var DescriptionPD = ThisED.PropertyDefs.AddNew();
            DescriptionPD.Name = "Description";
            DescriptionPD.FriendlyName = "Description";
            DescriptionPD.DatabaseColumnName = "Description";
            DescriptionPD.Purpose = "A somewhat user-friendly description of this permission.";
            DescriptionPD.DataType = "string";
            DescriptionPD.SqlDataType = "varchar";
            DescriptionPD.SqlDataLength = 250;
            DescriptionPD.IsNullable = false;



        }

        #endregion

        #endregion

        #region EntityDefExists

        /// <summary>
        /// Determines if an entity definition exists in this domain based on the identified name.
        /// </summary>
        public bool EntityDefExists(string name)
        {
            return this.EntityDefs.Find(name).IsSomething();
            //var ThisED = this.EntityDefs.Find(name);
            //return ThisED.IsSomething();

            //foreach (var ThisED in this.EntityDefs)
            //    if (ThisED.Name == name) return true;
            //return false;
        }

        #endregion

        #region FindPropertyDef

        /// <summary>
        /// Finds and returns the PropertyDef in this Domain with the identified id.  If not
        /// found, null is returned.
        /// </summary>
        public PropertyDef FindPropertyDef(Guid id)
        {
            foreach (var ThisED in this.EntityDefs)
                foreach (var ThisPD in ThisED.PropertyDefs)
                    if (ThisPD.ID == id) return ThisPD;

            return null;
        }

        public PropertyDef FindPropertyDef(string EntityDefName, string propertyDefintionName)
        {
            var TargetED = this.EntityDefs.Find(EntityDefName);
            if (TargetED != null)
                return TargetED.PropertyDefs.Find(propertyDefintionName);
            else
                return null;
        }
        #endregion

        #region DomainNamespace

        /// <summary>
        /// Gets the namepace used for all domain classes.
        /// </summary>
        public string DomainNamespace 
        {
            get { return this.Namespace + ".Domain"; }
        }

        #endregion

        #region FrameworkNamespace

        /// <summary>
        /// Gets the namepace used for all domain framework classes.
        /// </summary>
        public string FrameworkNamespace
        {
            get { return this.DomainNamespace + ".Framework"; }
        }

        #endregion

        #region ServicesNamespace

        /// <summary>
        /// Gets the namepace used for all services classes.
        /// </summary>
        public string ServicesNamespace
        {
            get { return this.Namespace + ".Services"; }
        }

        #endregion

        #region AggregatesNamespace

        /// <summary>
        /// Gets the namepace used for all domain Aggregates classes.
        /// </summary>
        public string AggregatesNamespace
        {
            get { return this.DomainNamespace + ".Aggregates"; }
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return this.Name;
        }

        #endregion

    }
}

